package com.yuxiao.dynmic.scheduled.conf;

import com.yuxiao.dynmic.scheduled.annotation.DynamicScheduled;
import com.yuxiao.dynmic.scheduled.job.DynamicJobManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;

/**
 * {@link DynamicScheduled DynamicScheduled解析器}
 *
 * @author yangjunwei
 * @date 2022-01-26 21:21
 */
@Slf4j
@Configuration
public class DynamicScheduledPostProcessor implements BeanPostProcessor, EmbeddedValueResolverAware {


    @Autowired
    private DynamicJobManager dynamicJobManager;


    private StringValueResolver stringValueResolver;


    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.stringValueResolver = resolver;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!(bean instanceof AopInfrastructureBean) && !(bean instanceof TaskScheduler) && !(bean instanceof ScheduledExecutorService)) {
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
            if (AnnotationUtils.isCandidateClass(targetClass, List.of(DynamicScheduled.class))) {
                Map<Method, Set<DynamicScheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<DynamicScheduled>>) (method) -> {
                    DynamicScheduled scheduledAnnotation = AnnotatedElementUtils.getMergedAnnotation(method, DynamicScheduled.class);
                    return scheduledAnnotation != null ? Set.of(scheduledAnnotation) : null;
                });
                annotatedMethods.forEach((method, scheduledAnnotations) -> {
                    scheduledAnnotations.forEach((scheduled) -> {
                        this.processScheduled(scheduled, method, bean);
                    });
                });
            }
            return bean;
        } else {
            return bean;
        }
    }


    protected void processScheduled(DynamicScheduled scheduled, Method method, Object bean) {
        try {

            String cron = scheduled.cron();
            String name = scheduled.name();
            String cronVal = "";
            if (this.stringValueResolver != null) {
                name = stringValueResolver.resolveStringValue(name);
                cronVal = stringValueResolver.resolveStringValue(cron);
            }

            if (StringUtils.hasLength(cron)) {
                if (!"-".equals(cron)) {
                    Runnable runnable = this.createRunnable(bean, method);
                    String key;
                    if(cron.equals(cronVal)) {
                        key = name;
                    } else {
                        key = String.format("%s:%s", name, cron);
                    }
                    dynamicJobManager.register(key, cronVal, runnable);
                }
            }

        } catch (IllegalArgumentException var25) {
            throw new IllegalStateException("Encountered invalid @DynamicScheduled method '" + method.getName() + "': " + var25.getMessage());
        }
    }


    protected Runnable createRunnable(Object target, Method method) {
        Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @DynamicScheduled");
        Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
        return new ScheduledMethodRunnable(target, invocableMethod);
    }


}
